#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Author  : Leo.W
# @Date    : 2024-1-19 14:30
# @Software: PyCharm
import datetime
import math
import string

"""
文本序列类型 --- str
在 Python 中处理文本数据是使用 str 对象，也称为 字符串。 字符串是由 Unicode 码位构成的【不可变序列】。
注意：由于不存在单独的“字符”类型，对字符串做索引操作将产生一个长度为 1 的字符串。 也就是说，对于一个非空字符串 s, s[0] == s[0:1]。
字符串字面值有多种不同的写法：
"""
# 单引号: '允许包含有 "双" 引号'
# 双引号: "允许嵌入 '单' 引号"
# 三重引号: '''三重单引号''', """三重双引号"""
# 使用三重引号的字符串可以跨越多行 —— 其中所有的空白字符都将包含在该字符串字面值中
# 作为单一表达式组成部分，之间只由空格分隔的多个字符串字面值会被隐式地转换为单个字符串字面值
s = "Hello" "World"
print(s)  # HelloWorld

"""
class str(object='')
class str(object=b'', encoding='utf-8', errors='strict')
返回 object 对象的字符串表示形式：
 - 如果 object 为字符串,则返回其本身
 - 如果 object 不是字符串,则返回  type(object).__str__(object) 的结果 -> 对象的“非正式”字符串表示
 - 如果 object 没有定义 __str__() 方法，则返回 repr(object)结果 -> 对象的“官方”字符串表示。
 
内置函数 repr() 内调用 object.__repr__(self) 以输出一个对象的“官方”字符串表示。形式如 <...描述信息...> 
内置类型 object 所定义的默认实现会调用 object.__repr__()
如果一个类定义了 __repr__() 但未定义 __str__()，则该类的实例的 “非正式”字符串表示 也会使用 __repr__()

str() 函数返回供人阅读的值，repr() 则生成适于解释器读取的值（如果没有等效的语法，则强制执行 SyntaxError）。
"""


# 类可以通过定义 __str__ 或 __repr__() 方法来 str() 和 repr() 函数为它的实例所返回的字符串描述内容
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return "Person {{name:{},age:{}}}".format(self.name, self.age)

    def __repr__(self):
        return f"<{self.__class__.__name__}(name='{self.name}',age={self.age})>"


p = Person("张三", 18)
"""
当 __str__ 和 __repr__ 都未定义时输出 <类名 object at 内存地址>： <__main__.Person object at 0x00000282D1662320>
当定义了 __str__ 时输出： Person {name:张三,age:18} 
当定义了 __repr__ 时输出：<Person(name='张三',age=18)>
"""
print(str(p), repr(p))

# 数字、列表或字典等结构的值，使用str() 和 repr()函数输出的表现形式是一样的
d = {"name": "Tomy", "age": 19}
t = (1, 2, 3)
l = ["A", "B", "C"]
print(str(d), str(t), str(l))
print(repr(d), repr(t), repr(l))

# 字符串使用str() 和 repr()函数输出的结果是不同的：
s = "Hello world\n"
print(str(s), repr(s))  # repr()函数输出字符串内容会使用 '' 扩起来并保留转义符 \n 。
# Hello world
# 'Hello world\n'

# ========== 格式化字符串 ========== #
"""
----- 方式一：f-字符串格式：f"xxxx{表达式}" -----
格式化字符串字面值（简称为 f-字符串）在字符串前加前缀 f 或 F，通过 {expression} 表达式，把 Python 表达式的值添加到字符串内。
----- 方式二：str.format() 方法格式化字符串 -----
"""
# f-字符串, {math.pi:.3f} 表达式 math.pi 后面 : 后 .3f 表示格式化输出 将 pi 舍入到小数点后三位
print(f"Pi value is {math.pi:.3f}")  # Pi value is 3.142

"""
----- 用于整数格式化的表示类型 -----
'b'     二进制格式。 输出以 2 为基数的数字。
'c'     字符。在打印之前将整数转换为相应的unicode字符。
'd'     十进制整数。 输出以 10 为基数的数字。
'o'     八进制格式。 输出以 8 为基数的数字。
'x'     十六进制格式。 输出以 16 为基数的数字，使用小写字母表示 9 以上的数码。
'X'     十六进制格式。 输出以 16 为基数的数字，使用大写字母表示 9 以上的数码。 在指定 '#' 的情况下，前缀 '0x' 也将被转为大写形式 '0X'。
'n'     数字。 这与 'd' 相似，不同之处在于它会使用当前区域设置来插入适当的数字分隔字符。
None    和 'd' 相同。
"""
x = 1010
print(
    f"{x:d} {x:b} {x:#b} {x:o} {x:x} {x:#X}"
)  # 1010 1111110010 0b1111110010 1762 3f2 0X3F2

"""
----- 用于float 和 Decimal 值的可用表示类型有: -----
'e'     科学计数法。 
'E'     科学计数法。 与 'e' 相似，不同之处在于它使用大写字母 'E' 作为分隔字符。
'f'     定点表示法。如：.2f表示保留2为小数。如未指定精度，则会对 float 采用小数点之后 6 位精度，而对 Decimal 则使用大到足够显示所有系数位的精度。 如果小数点之后没有数位，则小数点也会被略去，除非使用了 # 选项。
'F'     定点表示。 与 'f' 相似，但会将 nan 转为 NAN 并将 inf 转为 INF。
'g'     常规格式。 对于给定精度 p >= 1，这会将数值舍入到 p 个有效数位，再将结果以定点表示法或科学计数法进行格式化，具体取决于其值的大小。 精度 0 会被视为等价于精度 1。

准确的规则如下：假设使用表示类型 'e' 和精度 p-1 进行格式化的结果具有指数值 exp。 那么如果 m <= exp < p，其中 m 以 -4 表示浮点值而以 -6 表示 Decimal 值，该数字将使用类型 'f' 和精度 p-1-exp 进行格式化。 否则的话，该数字将使用表示类型 'e' 和精度 p-1 进行格式化。 在两种情况下，都会从有效数字中移除无意义的末尾零，如果小数点之后没有余下数字则小数点也会被移除，除非使用了 '#' 选项。
如未指定精度，会对 float 采用 6 个有效数位的精度。 对于 Decimal，结果的系数会沿用原值的系数数位；对于绝对值小于 1e-6 的值以及最小有效数位的位值大于 1 的数值将会使用科学计数法，在其他情况下则会使用定点表示法。
正负无穷，正负零和 nan 会分别被格式化为 inf, -inf, 0, -0 和 nan，无论精度如何设定。

'G'     常规格式。 类似于 'g'，不同之处在于当数值非常大时会切换为 'E'。 无穷与 NaN 也会表示为大写形式。
'n'     数字。 这与 'g' 相似，不同之处在于它会使用当前区域设置来插入适当的数字分隔字符。
'%'     百分比。 将数字乘以 100 并显示为定点 ('f') 格式，后面带一个百分号。

"""
# 修饰符（转换旗标） !s、!r、!a 可以在格式化前转换表达式的值：
# '!s' 会对值调用 str()，'!r' 调用 repr() 而 '!a' 则调用 ascii()
# {expr!a} -> ascii(expr) ，{expr!s} -> str(expr)，{expr!r} -> repr(expr)
print(f"ascii -> {s!a}")  # ascii -> 'Hello world\n'
print(f"str -> {s!s}")  # str -> Hello world
print(f"repr -> {s!r}")  # repr -> 'Hello world\n'
# 在Python 3.8版本f-string加入 等号 '=' 说明符，形式为 f'{expr=}' 的f-字符串将扩展表示为【表达式文本】。
# 如 expr = 'XXX' 那么 f'{expr=}' --> "expr='XXX'" 相当于将 {expr=} 转换成了表达式文本 "expr='XXX'"
# 可以是使用 {expr=:} 使用 =: 将表达式计算结果格式化输出。: 后为表达式计算结果的输出格式。
# 同样支持修师符： {expr=!r}，{expr=!s},{expr=!a}
theta = 30
print(f"{theta=}  {math.cos(math.radians(theta))=:.3f}")
# 输出：theta=30  math.cos(math.radians(theta))=0.866
bugs, count, area, price = "roaches", 13, "living room", 10245.2368
print(
    f"Debugging {bugs=} {count=} {area=!r} {price=:,.2f}"
)  # Debugging bugs='roaches' count=13 area='living room' price=10,245.24

# ========== str.format(*args, **kwargs) 语法 ========== #
# 使用位置参数 *args:
print("{0}, {1}, {2}".format("a", "b", "c"))
print("{}, {}, {}".format("a", "b", "c"))  # 3.1+ only
print("{2}, {1}, {0}".format("a", "b", "c"))
print("{2}, {1}, {0}".format(*"abc"))  # unpacking argument sequence
print("{0}{1}{0}".format("abra", "cad"))  # arguments' indices can be repeated

# 使用关键字参数 **kwargs:
st = "Coordinates: {latitude}, {longitude}"
print(
    st.format(latitude="37.24N", longitude="-115.81W")
)  # Coordinates: 37.24N, -115.81W

coord = {"latitude": "37.24N", "longitude": "-115.81W"}
print(st.format(**coord))  # Coordinates: 37.24N, -115.81W

# 可以通过传递字典，并用方括号 '[]' 访问键来完成格式化
table = {"Sjoerd": 4127, "Jack": 4098, "Dcab": 8637678}
print("Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; Dcab: {0[Dcab]:d}".format(table))
# **标记关键字参数进行解包，效果同上
print("Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}".format(**table))
# 使用 str.format_map(mapping) 进行格式化，效果同上
print("Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}".format_map(table))
# Jack: 4098; Sjoerd: 4127; Dcab: 8637678

# 支持 修饰符（转换旗标）
print("repr() shows quotes: {!r}; str() doesn't: {!s}".format("test1", "test2"))

# 格式化输出数字
print("Pi value is {:.4f}".format(math.pi))
print("int: {0:d};  hex: {0:x};  oct: {0:#o};  bin: {0:b}".format(10))
# with 0x, 0o, or 0b as prefix:
print("int: {0:#d};  hex: {0:#x};  oct: {0:#o};  bin: {0:#b}".format(10))
# 使用逗号作为千位分隔符:
print("{:,}".format(1234567890))
# 表示为百分数:
points = 19
total = 22
print("Correct answers: {:.2%}".format(points / total))
"""
输出：
Pi value is 3.1416
int: 10;  hex: a;  oct: 0o12;  bin: 1010
int: 10;  hex: 0xa;  oct: 0o12;  bin: 0b1010
1,234,567,890
Correct answers: 86.36%
"""

# 使用特定类型的专属格式化:
d = datetime.datetime.now()
print("{:%Y-%m-%d %H:%M:%S}".format(d))  # 2010-07-04 12:15:58

# 对齐文本以及指定宽度:
print("{:<30}".format("left aligned"))
print("{:>30}".format("right aligned"))
print("{:^30}".format("centered"))
print("{:*^30}".format("centered"))
"""
输出：
left aligned                  
                 right aligned
           centered           
***********centered***********
"""

# ========== string 模块 ========== #
"""
string 模块为 Python 内置模块，封装了常见的字符串操作和字符串常量。
"""
# string 模块中定义的字符串常量：
# whitespace = ' \t\n\r\v\f'
# ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'
# ascii_uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
# ascii_letters = ascii_lowercase + ascii_uppercase
# digits = '0123456789'
# hexdigits = digits + 'abcdef' + 'ABCDEF'
# octdigits = '01234567'
# punctuation = r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
# printable = digits + ascii_letters + punctuation + whitespace

print(string.ascii_lowercase)
print(string.ascii_uppercase)

# ========== string.Template 模板字符串 ========== #
"""
class string.Template(template): 
构造一个有指定字符串模板的对象，模板字符串支持基于 $ 的替换。
template: 字符串模板。其中使用 $keyword 的格式设置替换占位符（以 $ 符开头）。
替换占位符规则如下：
 - $identifier 为替换占位符，它会匹配一个名为 "identifier" 的映射键。 
   在默认情况下，"identifier" 限制为任意 ASCII 字母数字（包括下划线）组成的字符串，不区分大小写，以下划线或 ASCII 字母开头。 
   在 $ 字符之后的第一个非标识符字符将表明占位符的终结。
 - ${identifier} 等价于 $identifier。 当占位符之后紧跟着有效的但又不是占位符一部分的标识符字符时需要使用，例如 "${noun}ification"。
 - $$ 为转义符号；它会被替换为单个的 $。注意：字符串的其他位置出现 $ 将导致引发 ValueError。
 
Template 实例的属性：
 - template :构造器的 template 参数被传入的对象。 一般来说，不应该修改它，但并不强制要求只读访问。

Template 实例的方法：
 + substitute(mapping={}, /, **kwds)：
   执行模板替换，返回一个新字符串。 mapping 为任意字典类对象，其中的键将匹配模板中的占位符。 
   或者你也可以提供一组关键字参数，其中的关键字即对应占位符。 当同时给出 mapping 和 kwds 并且存在重复时，则以 kwds 中的占位符为优先。

 + safe_substitute(mapping={}, /, **kwds)：
   类似于 substitute()，不同之处是如果有占位符未在 mapping 和 kwds 中找到，不是引发 KeyError 异常，而是将原始占位符不加修改地显示在结果字符串中。
   另一个与 substitute() 的差异是任何在其他情况下出现的 $ 将简单地返回 $ 而不是引发 ValueError。
   此方法被认为“安全”，因为虽然仍有可能发生其他异常，但它总是尝试返回可用的字符串而不是引发一个异常。 
   从另一方面来说，safe_substitute() 也可能根本算不上安全，因为它将静默地忽略错误格式的模板，例如包含多余的分隔符、不成对的花括号或不是合法 Python 标识符的占位符等等。

 + is_valid()：
   如果模板有会导致 substitute() 引发 ValueError 的无效占位符则返回假值。在 3.11 版本加入

 + get_identifiers()：
   返回模板中有效占位符的列表，按它们首次出现的顺序排列，忽略任何无效标识符。
"""
ts = string.Template("I'm $name,and I am $age years old.")
print(ts.substitute({"name": "Jack", "age": 18}))  # I'm Jack,and I am 18 years old.
print(ts.substitute(name="Tome", age=21))  # I'm Tome,and I am 21 years old.
dic = dict(name="Jerry")
# 使用 safe_substitute，如果参数中没有找到相应的 占位符，则不替直接输出换占位符
print(ts.safe_substitute(dic))  # I'm Jerry,and I am $age years old.
try:
    # substitute()方法没有找到相应占位符的映射时，则抛出异常
    print(ts.substitute(dic))  # KeyError: 'age'
except KeyError as err:
    print(err)


"""
进阶用法：
派生 Template 的子类来自定义占位符语法、分隔符，或用于解析模板字符串的整个正则表达式。 
在派生类中重载类属性：
delimiter       :用来表示占位符的起始的分隔符的字符串字面值。 默认值为 $。注意此参数【不能】为正则表达式
idpattern       :用来描述不带花括号的占位符的模式的正则表达式。 默认值为正则表达式 (?a:[_a-z][_a-z0-9]*)。
                 如果设置了此属性并且 braceidpattern 为 None 则此模式也将作用于带花括号的占位符。
braceidpattern  :此属性类似于 idpattern 但是用来描述带花括号的占位符的模式。
                 设置该属性将允许你为带花括号和不带花括号的占位符定义不同的模式。

"""
